import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

// class monitor, keeps track of all virtual machines and handles user input
public class Monitor {

	// keep track of the virtual machines
	private ArrayList<VirtualMachine> VMs;
	
	// constructor, initialize the monitor
	public Monitor() throws IOException {
		
		// initialize the virtual machines
		VMs = new ArrayList<VirtualMachine>();
		// create a local runtime environment
		Runtime run = Runtime.getRuntime();
		// fetch running VMs
		Process pr = run.exec("onevm list -l ID");
		try {
			pr.waitFor() ;
		} catch (InterruptedException e) {
			System.out.println("InterruptedException occured while executing a shell command on this virtual machine...\nThe command was:\n****");
			e.printStackTrace();
		}
		// check which machines are running
		BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream())) ;
		String line;
		buf.readLine();
		// create new virtual machines using the identifiers of the running virtual machines
		while ((line = buf.readLine()) != null && line.length() > 1) {
			int identifier = Integer.parseInt(line.substring(1));
			VMs.add(new VirtualMachine(identifier));
		}
	}
	
	public void removeAllVMs(){
		try {
			removeVMs(VMs.size());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	// function to show the virtual machines currently active
	public void showVMs() {
		System.out.println("+----+-------+--------------+---------+--------+");
		System.out.println("|  # | VM ID | IP           | STATUS  | THREAD |");
		System.out.println("+----+-------+--------------+---------+--------+");
		for(int i = 1; i <= VMs.size(); i++) {
			System.out.println("|"+("   "+i).substring((i+"").length())+" | "+VMs.get(i-1).toString()+" |");
		}
		System.out.println("+----+-------+--------------+---------+--------+");
	}
	
	// boot j new virtual machines
	public void addVMs(int j) throws IOException {
		int addedMachines = 0;
		for(int i = 0; i < j; i++) 
		  if(VMs.size() < 100) {
			  VMs.add(new VirtualMachine(0));
			  addedMachines++;
		  }
		System.out.println(addedMachines+" virtual machines added.");
	}
	
	// remove j virtual machines (shutting them down)
	public void removeVMs(int j) throws IOException {
		// check if j is a feasible number
		if(j > VMs.size()) {
			System.out.println("There are only "+VMs.size()+" virtual machines. Shutting all down...");
			j = VMs.size();
		}
		int oldj = j;
		System.out.println("Shutting down "+j+" virtual machines...");
		// shut them down from end to front (for index pointer sake ;-) )
		for(int i = VMs.size() - 1; i >= 0 && j > 0; i--) {
			// we cannot shutdown busy machines
			if(!VMs.get(i).isBusy()) {
				VMs.get(i).shutDown();
				VMs.remove(i);
				j--;
			}
		}
		if(j > 0) System.out.println("Some VMs are busy, could not shut down "+j+" virtual machines...");
		else System.out.println(oldj+" virtual machines shut down.");
	}

	// function to count words in file "file"
	public void countWords(String file) throws FileNotFoundException, IOException {
		// check runtime
		long startTime = System.currentTimeMillis();
		// check how many machines can actually be used at the moment (not booting etc)
		int readyMachines = 0;
		for(int i = 0; i < this.VMs.size(); i++)
			if(VMs.get(i).sshReady())
				readyMachines++;
		
		// check the size of the input file
		String size = this.executeCommand("stat -c%s "+file);
		if(size.equals("")) {
			System.out.println("No such file...");
			return;
		}
		// divide the input file over the virtual machines
		this.executeCommand("split -b "+(Integer.parseInt(size) / readyMachines + 1)+" '"+file+"' temp");
		
		// create a VMExecutor (which opens a new thread) for parallel processing
		ArrayList<VMExecutor> VMEs = new ArrayList<VMExecutor>();
		for(int i = 0; i < this.VMs.size(); i++) {
			// create the VMExecutor with the PART of the file created above
			if(VMs.get(i).sshReady()) VMEs.add(new VMExecutor(VMs.get(i), "temp"+"abcdefghijklmnopqrstuvwxyz".charAt(i/26)+"abcdefghijklmnopqrstuvwxyz".charAt(i%26)));
		}
		
		// wait till all the virtual machines are finished calculating
		boolean done = false;
		int count = 0;
		int finishedMachines = 0;
		while(!done) {
			done = true;
			for(int i = 0; i < VMEs.size(); i++) 
				// default value is -1, so >=0 indicates that the virtual machine is done
				if(VMEs.get(i).getValue() < 0)
					done = false;
				else //report result of the current machine, including the intermediate result!
				{
					count+= VMEs.get(i).getValue();
					finishedMachines++;
					System.out.println("Vowels counted up till now, "+count+". We are at "+(VMEs.size()/finishedMachines)+"%");
				}
		}
		
		// accumulate the numbers, exciting! But we already did this in the other for loop!
//		for(int i = 0; i < VMEs.size(); i++) 
//			count+=VMEs.get(i).getValue();
		
		// calculate runtime and return output
		long endTime   = System.currentTimeMillis();
		long totalTime = endTime - startTime;
		this.executeCommand("rm temp*");
		System.out.println("Vowels counted, "+count+" vowels found in "+totalTime+" miliseconds using "+VMEs.size()+" virtual machines.");
	}
	
	// function to execute commands on the local DAS4 system for this instance
	private String executeCommand(String command) throws IOException {
		// create runtime environment
		Runtime run = Runtime.getRuntime();
		// prepare the command
		String[] cmd = {"/bin/sh", "-c", command};
		Process pr = run.exec(cmd);
		try {
			pr.waitFor() ;
		} catch (InterruptedException e) {
			System.out.println("InterruptedException occured while executing a shell command...\nThe command was:\n****"+command);
			e.printStackTrace();
		}
		
		// read input
		BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream())) ;
		String line;
		String output = "";
		while ((line = buf.readLine()) != null) {
			output+=(line) ;
		}
		return output;
	}

	// function to delete all failed instances of virtual machines
	public void clear() throws IOException {
		for(int i = 0; i < VMs.size(); i++) {
			if(VMs.get(i).status().equals("fail")) {
				VMs.get(i).delete();
				VMs.remove(i);
			}
		}
	}
}
